.. _глава_44_start:

Время жизни и фундаментальные операции
======================================

.. index::
    время жизни переменной

Вскоре мы рассмотрим структуры, базовую функцию, которая позволяет программисту определять
типы, специфичные для приложения. Структуры предназначены для объединения фундаментальных типов и других
структур вместе для определения типов более высокого уровня, которые ведут себя в соответствии с особыми
потребностями программ. После structs мы узнаем о классах, которые являются основой
возможностей объектно-ориентированного программирования D.
Прежде чем перейти к структурам и классам, лучше сначала поговорить о некоторых
важных концепциях. Эти концепции помогут понять структуры и классы
и некоторые из их различий.
Мы называли любой фрагмент данных, представляющий концепцию в программе
, переменной. В нескольких местах мы ссылались на переменные struct и class
конкретно как на объекты. Я продолжу называть оба этих понятия переменными в
этой главе.
Хотя в этой главе рассматриваются только фундаментальные типы, срезы и ассоциативные
массивы, эти концепции применимы и к пользовательским типам.


Время жизни переменной
----------------------
Время между моментом определения переменной и моментом ее завершения - это время
время жизни этой переменной. Хотя для многих типов это так и есть, момент когда переменная становится недоступной и удаление переменной
не обязательно происходит в одно и то же время.
Из главы "Область имен" (:ref:`Видимость имени<Видимость имени>`) вы помните, как переменные становятся
становятся недоступными. В простых случаях выход из области видимости, в которой была определена переменная
сделает эту переменную недоступной.
В качестве напоминания рассмотрим следующий пример:

.. code-block:: d

    void speedTest()
    {
        int speed; // определяем переменную
        foreach (i; 0 .. 10)
        {
            speed = 100 + i;
            // ...
        }
    } // ← 'speed' переменная недоступна с этого момента


Жизненный цикл переменной speed в этом коде завершается функцией speedTry
заканчивается. Здесь есть только одна переменная, которая принимает 10 различных значений в диапазоне от 100 до 109.
В следующем коде ситуация с точки зрения жизненных процессов совершенно иная:

.. code-block:: d

    void speedTest()
    {
        foreach (i; 0 .. 10)
        {
            int speed = 100 + i; // определяем 10 переменных
            // ...
        } // ←Время жизни всех переменных заканчивается здесь
    }


В этом коде десять отдельных переменных, каждая из которых принимает одно значение. На сайте
каждой итерации цикла начинает свою жизнь новая переменная, которая в конечном итоге заканчивается в
в конце каждой итерации.

.. index::
    время жизни параметра

Время жизни параметра
---------------------

Время жизни параметра зависит от его квалификаторов:
ref: параметр является псевдонимом фактической переменной, указанной при
ref-параметры не влияют на время жизни фактических переменных.
переменных.
in: Для типов значений время жизни параметра начинается при входе в функцию
функции и заканчивается при выходе из нее. Для ссылочных типов время жизни параметра
такое же, как и для ref.
out: Как и в случае с ref, параметр является просто псевдонимом фактической переменной, которая
указанной при вызове функции. Единственное отличие заключается в том, что переменная устанавливается
в свое значение .init автоматически при входе в функцию.
lazy: Жизнь параметра начинается с момента его фактического использования и
и заканчивается сразу после этого.
В следующем примере используются эти четыре типа параметров и объясняется их
время жизни в комментариях к программе:

.. code-block:: d

    void main()
    {
        int main_in; /* Значение main_in копируется в параметры */
        int main_ref; /* main_ref передаётся в функцию напрямую. */
        int main_out; /* main_out передаётся в функцию напрямую.
                        * её значение будет равно int.init
                        * до входа в функцию. */
        foo(main_in, main_ref, main_out, aCalculation())
    }

    void foo(in int p_in, /* Время жизниВремя жизни p_in начинается при
                            * входа в функцию и заканчивается при
                            * выходе из функции. */
        ref int p_ref, /* p_ref - это псевдоним main_ref. */out int p_out, /* p_out является псевдонимом main_out. Его
                                * значение устанавливается в int.init при
                                * входе в функцию. */
        lazy int p_lazy)
    { /* Время жизни p_lazy начинается с момента
                                    * его использования и заканчивается, когда его использование
                                    * заканчивается. Его значение вычисляется путем
                                    * вызова aCalculation() каждый раз, когда
                                    * p_lazy используется в функции. */
        // ...
    }

    int aCalculation()
    {
        int result;
        // ...
        return result;
    }

.. index::
  инициализация
  финализация
  присвоение

Фундаментальные операции
-------------------------

Независимо от типа переменной, на протяжении всего времени ее существования существует три основных операции
во время жизни переменной:
* Инициализация: Начало ее жизни.
* Финализация: Окончание жизни.
* Присвоение: Изменение его значения в целом.
Чтобы считаться объектом, он должен быть сначала инициализирован. Для некоторых типов могут быть предусмотрены
операции для некоторых типов. Значение переменной может меняться в течение ее жизни.

**Инициализация**

Каждая переменная должна быть инициализирована перед использованием. Инициализация включает в себя два
шага:
1. Резервирование места для переменной: Это место, где значение переменной
хранится в памяти.
2. Построение: Установка первого значения переменной в этом пространстве (или первых
значения членов структур и классов).
Каждая переменная живет в отведенном для нее месте в памяти. Часть кода
который генерирует компилятор, связан с резервированием места для каждой переменной.
Рассмотрим следующую переменную:

    int speed = 123;

Как мы уже видели в главе "Типы значений и ссылочные типы" (:ref:`Типы значения и ссылочные типы<глава_34_start>`), мы
можем представить, что эта переменная живет в какой-то части памяти:

.. image:: images/44.3_1.png

Позиция в  памяти, в которую помещается переменная, называется ее адресом. В некотором смысле,
переменная живет по этому адресу. Когда значение переменной изменяется, новое значение
новое значение сохраняется в том же месте:

.. code-block:: d

    ++speed;

Новое значение будет находиться там же, где и старое:

.. code-block:: console

.. image:: images/44.3_2.png


Конструирование необходимо для подготовки переменных к использованию. Поскольку переменная не может быть
надежно использоваться до конструирования, оно выполняется компилятором
автоматически.
Переменные могут быть сконструированы тремя способами:
* По значению по умолчанию: когда программист не указывает значение
в явном виде
* Копированием: когда переменная строится как копия другой переменной того же типа.
того же типа
* По конкретному значению: когда программист явно указывает значение.
Когда значение не указано, значением переменной будет значение по умолчанию
ее типа, то есть ее значение .init.

.. code-block:: d

    int speed;

Значение скорости выше - int.init, которое равно нулю. Естественно, что
переменная, которая строится по значению по умолчанию, может иметь другие значения в течение своего
(если она не является неизменяемой).

.. code-block:: d

    File file;

Согласно приведенному выше определению, переменная file - это объект File, который еще не
ассоциирован с реальным файлом в файловой системе. Его нельзя использовать до тех пор, пока он не будет изменен
чтобы быть связанной с файлом.
Иногда переменные создаются как копия другой переменной:

.. code-block:: d

    int speed = otherSpeed;

скорость выше построена по значению otherSpeed.
Как мы увидим в последующих главах, эта операция имеет другое значение для переменных класса

.. code-block:: d

    auto classVariable = otherClassVariable;

это фундаментальное различие с классами: Хотя speed и otherSpeed являются
разные значения, classVariable и otherClassVariable предоставляют доступ к
одному и тому же значению. В этом заключается фундаментальное различие между типами значений и
ссылочными типами.
Наконец, переменные могут быть построены по значению выражения
совместимого типа:

.. code-block:: d

    int speed = someCalculation();

скорость выше будет построена по возвращаемому значению функции someCalculation().

**Финализация**

Финализация - это заключительные операции, выполняемые над переменной и освобождающие
ее памяти:
#. Уничтожение: Заключительные операции, которые должны быть выполнены для переменной.
#. Восстановление памяти переменной: Освобождение памяти, в которой
на котором жила переменная.

Для простых фундаментальных типов не нужно выполнять никаких завершающих операций. Например,
например, значение переменной типа int не устанавливается обратно в ноль. Для таких
переменных происходит только освобождение памяти, чтобы потом использовать ее для других
переменных позже.
С другой стороны, некоторые типы переменных требуют специальных операций при
финализации. Например, объект File должен записать символы, которые
которые все еще находятся в его выходном буфере, на диск и уведомить файловую систему о том, что она больше не использует
файл. Эти операции являются уничтожением объекта File.
Финальные операции с массивами находятся на несколько более высоком уровне: Прежде чем завершить массив,
сначала уничтожаются его элементы. Если элементы относятся к простому фундаментальному типу
например int, то для них не существует специальных финальных операций. Если же элементы относятся к
struct или тип класса, требующий финализации, то эти операции выполняются
для каждого элемента.
Ассоциативные массивы похожи на массивы. Кроме того, ключи также могут быть
финализированы, если они относятся к типу, требующему уничтожения

**Сборщик мусора:**

D - это язык со сборщиком мусора. В таких языках
финализация объекта не должна инициироваться программистом явно. Когда
время жизни переменной заканчивается, ее финализация автоматически обрабатывается сборщиком мусора
сборщиком мусора. Мы рассмотрим сборщик мусора и специальное управление памятью
в одной из следующих глав (стр. 667).
Переменные могут быть завершены двумя способами:

* Когда заканчивается время жизни: Финализация происходит в конце жизни переменной.
* Через некоторое время в будущем: Финализация происходит через неопределенное время в
  в будущем сборщиком мусора.

То, каким из двух способов будет завершена переменная, зависит в первую очередь от ее типа.
Некоторые типы, такие как массивы, ассоциативные массивы и классы, обычно уничтожаются
сборщиком мусора в определенное время в будущем.

Присваивание
Другая фундаментальная операция, которую переменная переживает в течение своего существования, - это
присвоение.
Для простых фундаментальных типов присвоение - это просто изменение значения
переменной. Как мы видели выше на примере представления памяти, переменная int
начнет иметь значение 124 вместо 123. Однако в более общем случае
присваивание состоит из двух шагов, которые не обязательно выполняются в
следующем порядке:
* Уничтожение старого значения
* Создание нового значения
Эти два шага не важны для простых фундаментальных типов, которым не требуется
уничтожении. Для типов, которым требуется уничтожение, важно помнить, что
присваивание - это комбинация двух вышеописанных шагов.
